package com.bangcommunity.bbframe.orm;

import com.bangcommunity.bbframe.common.base.IEntity;
import com.bangcommunity.bbframe.common.proxy.AfterDelegateExecutor;
import com.bangcommunity.bbframe.common.proxy.BeforeDelegateExecutor;
import com.bangcommunity.bbframe.common.proxy.DelegateExecutorUtils;
import com.bangcommunity.bbframe.common.utils.lang.CollectionUtils;
import com.bangcommunity.bbframe.mybatis.criteria.AbstractorCriteria;
import com.bangcommunity.bbframe.mybatis.manager.BaseManager;
import com.bangcommunity.bbframe.mybatis.mapper.BaseMapper;
import com.bangcommunity.bbframe.springredis.cache.SecondLevelCache;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;

/**
 * @author tanghc
 * @date 18/1/3
 */
public class BaseManagerWithCache<PK extends Serializable, E extends IEntity<PK>, C extends AbstractorCriteria, D extends BaseMapper<PK, E, C>>
        extends BaseManager<PK, E, C, D> implements IBaseManagerWithCache<PK, E, C>, InitializingBean {

    protected String domainClassName = null;
    private static final int one_week_sec = 604800;

    @Autowired(required = false)
    protected Cache secondLevelCache = null;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteByPrimaryKey(PK id) {
        DelegateExecutorUtils.delegateExecute(new AfterDelegateExecutor<Object>() {
            @Override
            public Object doExecute(Map<String, Object> context) {
                BaseManagerWithCache.super.deleteByPrimaryKey(id);
                return null;
            }

            @Override
            public void doFinal(Map<String, Object> context, Object o, Exception e) {
                BaseManagerWithCache.this.delBaseCache(id);
            }
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int deleteByExample(C example) {
        return DelegateExecutorUtils.delegateExecute(new BeforeDelegateExecutor<Integer>() {
            @Override
            public void doBefore(Map<String, Object> context) {
                BaseManagerWithCache.this.delCacheByExample(example);
            }
            @Override
            public Integer doExecute(Map<String, Object> context) {
                return BaseManagerWithCache.super.deleteByExample(example);
            }

        });
    }

    @Override
    public List<E> selectByExample(C example) {
        return DelegateExecutorUtils.delegateExecute(new AfterDelegateExecutor<List<E>>() {
            @Override
            public List<E> doExecute(Map<String, Object> context) {
                return BaseManagerWithCache.super.selectByExample(example);
            }

            @Override
            public void doFinal(Map<String, Object> context, List<E> es, Exception e) {
                if (null == e) {
                    BaseManagerWithCache.this.batchPutBaseCache(es);
                }
            }
        });

    }

    @Override
    public E selectByPrimaryKey(PK id) {

        return DelegateExecutorUtils.delegateExecute(new AfterDelegateExecutor<E>() {
            @Override
            public E doExecute(Map<String, Object> context) {
                return BaseManagerWithCache.super.selectByPrimaryKey(id);
            }

            @Override
            public void doFinal(Map<String, Object> context, E e, Exception e1) {
                if (null == e1) {
                    putBaseCache(e);
                }
            }
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateByPrimaryKeySelective(E record) {
        DelegateExecutorUtils.delegateExecute(new AfterDelegateExecutor<Object>() {
            @Override
            public Object doExecute(Map<String, Object> context) {
                BaseManagerWithCache.super.updateByPrimaryKeySelective(record);
                return null;
            }

            @Override
            public void doFinal(Map<String, Object> context, Object o, Exception e) {
                BaseManagerWithCache.this.delBaseCache(record);
            }
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int updateByExampleSelective(E record, C example) {
        return DelegateExecutorUtils.delegateExecute(new AfterDelegateExecutor<Integer>() {
            @Override
            public Integer doExecute(Map<String, Object> context) {
                return BaseManagerWithCache.super.updateByExampleSelective(record, example);
            }

            @Override
            public void doFinal(Map<String, Object> context, Integer o, Exception e) {
                BaseManagerWithCache.this.delCacheByExample(example);
            }
        });
    }

    /**
     * 根据主键生成基本对象的缓存key值
     */
    @Override
    public String getCacheKey(PK pk) {
        return new StringBuilder().append(domainClassName).append("_").append(pk).toString();
    }

    /**
     * 根据对象生成基本对象的缓存key值
     */
    @Override
    public String getCacheKey(E record) {
        PK identifier = record.getIdentifier();
        return getCacheKey(identifier);
    }

    private void putBaseCache(Cache cache, E e) {
        try {
            cache.put(getCacheKey(e), e);
        } catch (Exception e1) {
            logger.warn("putBaseCache fail:{},{}", e, e1.getMessage());
        }
    }

    private void delBaseCache(Cache cache, PK pk) {
        try {
            cache.evict(getCacheKey(pk));
        } catch (Exception e) {
            logger.warn("delBaseCache by pk fail:{},{}", e, e.getMessage());
        }
    }

    private void delBaseCache(Cache cache, E e) {
        try {
            cache.evict(getCacheKey(e));
        } catch (Exception e1) {
            logger.warn("delBaseCache by obj fail:{},{}", e, e1.getMessage());
        }
    }

    /**
     * 设置基本对象缓存
     *
     * @param e
     */
    @Override
    public void putBaseCache(E e) {
        if (null != e) {
            Cache cache = getCache();
            if (null != cache) {
                putBaseCache(cache, e);
            }
        }
    }

    /**
     * 删除基本对象缓存
     *
     * @param e
     */
    @Override
    public void delBaseCache(E e) {
        if (null != e) {
            Cache cache = getCache();
            if (null != cache) {
                delBaseCache(cache, e);
            }
        }
    }

    /**
     * 批量删除基本对象缓存,根据主键
     *
     * @param pk
     */
    @Override
    public void delBaseCache(PK pk) {
        if (null != pk) {
            Cache cache = getCache();
            if (null != cache) {
                delBaseCache(cache, pk);
            }
        }
    }

    /**
     * 批量设置基本对象缓存
     *
     * @param es
     */
    @Override
    public void batchPutBaseCache(List<E> es) {
        if (CollectionUtils.isNotEmpty(es)) {
            Cache cache = getCache();
            if (null != cache) {
                es.parallelStream().forEach(e -> {
                    putBaseCache(cache, e);
                });
            }
        }
    }

    /**
     * 批量删除基本对象缓存
     *
     * @param es
     */
    @Override
    public void batchDelBaseCache(List<? extends E> es) {
        if (CollectionUtils.isNotEmpty(es)) {
            Cache cache = getCache();
            if (null != cache) {
                es.parallelStream().forEach(e -> {
                    delBaseCache(cache, e);
                });
            }
        }
    }

    /**
     * 批量删除基本对象缓存
     *
     * @param pks
     */
    @Override
    public void batchDelBaseCacheByPks(List<? extends PK> pks) {
        if (CollectionUtils.isNotEmpty(pks)) {
            Cache cache = getCache();
            if (null != cache) {
                pks.parallelStream().forEach(pk -> {
                    delBaseCache(cache, pk);
                });
            }
        }
    }

    private void delCacheByExample(C example) {
        List<E> es = super.selectByExample(example);
        batchDelBaseCache(es);
    }
    private Cache getCache(){
        return secondLevelCache;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if(secondLevelCache == null ){
            createSecondLevelCache();
        }
        domainClassName = ((Class<E>) ((ParameterizedType) getClass().getGenericSuperclass())
                .getActualTypeArguments()[1]).getSimpleName();
    }
    private void createSecondLevelCache(){
        SecondLevelCache secondLevelCacheInstance = new SecondLevelCache();
        secondLevelCacheInstance.setExpireAfterWriteSec(cacheExpireSec());
        secondLevelCache = secondLevelCacheInstance;
    }
    protected int cacheExpireSec(){
        return one_week_sec;
    }
}
